package com.ncr.travel.appmgr.client.presenter;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBoxBase;
import com.google.gwt.user.client.ui.ValueBoxBase;
import com.google.inject.Inject;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanCodex;
import com.ncr.nep.dashboardcore.rest.client.JSONCallback;
import com.ncr.travel.appmgr.client.ConfigurationItem;
import com.ncr.travel.appmgr.client.Constants;
import com.ncr.travel.appmgr.client.command.PostScreenConfigurationCommand;
import com.ncr.travel.appmgr.client.event.ConfigChangeEvent;
import com.ncr.travel.appmgr.client.event.ConfigChangeHandler;
import com.ncr.travel.appmgr.client.event.ConfirmConfigChangeEvent;
import com.ncr.travel.appmgr.client.event.ConfirmConfigChangeEventHandler;
import com.ncr.travel.appmgr.client.event.PreviewConfigChangeEvent;
import com.ncr.travel.appmgr.client.event.PreviewConfigChangeEventHandler;
import com.ncr.travel.appmgr.client.event.UndoConfigChangeEvent;
import com.ncr.travel.appmgr.client.event.UndoConfigChangeEventHandler;
import com.ncr.travel.appmgr.client.model.ScreenConfigurationFactory;
import com.ncr.travel.appmgr.client.mvp.BasePresenter;
import com.ncr.travel.appmgr.client.mvp.EventBus;
import com.ncr.travel.appmgr.client.service.AppManagerWebService;
import com.ncr.travel.appmgr.client.view.ConfigEditorView;
import com.ncr.travel.appmgr.client.view.FontOptionsView;
import com.ncr.travel.appmgr.client.view.FontOptionsWidget;
import com.ncr.travel.appmgr.client.view.TextView;
import com.ncr.travel.appmgr.model.shared.Alignment;
import com.ncr.travel.appmgr.model.shared.ErrorText;
import com.ncr.travel.appmgr.model.shared.FontOptions;
import com.ncr.travel.appmgr.model.shared.ScreenConfiguration;
import com.ncr.travel.appmgr.model.shared.Text;
import com.ncr.travel.appmgr.model.shared.TextEntry;

/**
 * @author ng185038
 * 
 */
public class TextPresenterImpl extends BasePresenter<TextView> implements TextPresenter {

	private ScreenConfiguration screenConfiguration;
	private Map<String, Text> originalTextConfigs;
	private Map<String, Text> modifiedTextConfigs;
    /**
     * Factory for screen config model objects.
     */
	private ScreenConfigurationFactory screenConfigurationFactory;
	
	private final AppManagerWebService webService;

	@Inject
	public TextPresenterImpl(AppManagerWebService webService, ScreenConfigurationFactory screenConfigurationFactory, EventBus eventBus, TextView textView) {
		super(eventBus, textView);
		System.out.println("Constructing TextPresenter");
		this.screenConfigurationFactory = screenConfigurationFactory;
		this.webService = webService;
		view.setChangeHandler(new TextConfigChangeHandler());
		view.setFocusHandler(new TextConfigFocusHandler());
		view.setClickHandler(new FontOptionsClickHandler());
		view.setPresenter(this);
		bind();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.ncr.travel.appmgr.client.presenter.TextPresenter#setScreenConfiguration
	 * (com.ncr.travel.appmgr.client.model.ScreenConfiguration)
	 */
	@Override
	public final void setScreenConfiguration(ScreenConfiguration screenConfiguration) {
		this.screenConfiguration = screenConfiguration;
		view.setScreenConfiguration(this.screenConfiguration);
	}

	@Override
	public void bind() {
		// TODO: S.O.P statements to be removed once testing is done
		System.out.println("(TextPresenter) Called bind()");
		registerHandler(eventBus.addHandler(ConfigChangeEvent.getType(), new ConfigChangeHandler() {
			public void onConfigChanged(ConfigChangeEvent event) {
				ScreenConfiguration screenConfig = event.getScreenConfiguration();
				if (screenConfig != null) {
					System.out.println("(TextPresenter) Configuration Changed:  " + screenConfig.getText());
					setConfiguration(screenConfig.getId(), screenConfig.getName(), screenConfig.getUrl(),
							screenConfig.getText());
				} else {
					resetConfiguration();
				}
			}
		}));
		registerHandler(eventBus.addHandler(ConfirmConfigChangeEvent.getType(), new ConfirmConfigChangeEventHandler() {
			public void onConfirm(ConfirmConfigChangeEvent event) {
				if (!ConfigurationItem.TEXT.equals(event.getConfigurationItem())) {
					// Dont process the event if the event is not generated by
					// Text widget
					return;
				}
				/*
				 * FIXME:The screenConfiguration being set to
				 * ConfigurationWidget is the configuration before
				 * modifications. But while confirming we need to send the
				 * modified configurations.
				 */
				// doConfirmConfigChanges(event.getText());
				doConfirmConfigChanges(getModifiedText());
			}
		}));
		registerHandler(eventBus.addHandler(UndoConfigChangeEvent.getType(), new UndoConfigChangeEventHandler() {
			public void onUndo(UndoConfigChangeEvent event) {
				if (!ConfigurationItem.TEXT.equals(event.getConfigurationItem())) {
					// Dont process the event if the event is not generated by
					// Text widget
					return;
				}
				doUndoConfigChanges();
			}
		}));
		registerHandler(eventBus.addHandler(PreviewConfigChangeEvent.getType(), new PreviewConfigChangeEventHandler() {
			public void onPreview(PreviewConfigChangeEvent event) {
				if (!ConfigurationItem.TEXT.equals(event.getConfigurationItem())) {
					// Dont process the event if the event is not generated by
					// Text widget
					return;
				}
				doPreviewConfigChanges();
			}
		}));

	}

	private void doUndoConfigChanges() {
		System.out.println("Text Undo config change event received");
	}

	private void doPreviewConfigChanges() {
		System.out.println("Text preview config change event received");
	}

	private void doConfirmConfigChanges(List<Text> textConfigs) {
		System.out.println("Text confirm config change event received");
		final ScreenConfiguration screenConfig = screenConfigurationFactory.screenConfiguration().as();
		screenConfig.setText(textConfigs);
		new PostScreenConfigurationCommand(screenConfig, webService, new JSONCallback<ScreenConfiguration>(screenConfigurationFactory, ScreenConfiguration.class, Constants.MOCK_SERVER_MODE) {

			@Override
			public void onSuccess(ScreenConfiguration screenConfig) {
				eventBus.fireEvent(new ConfigChangeEvent(screenConfig));
			}

			@Override
			public void onFailure(Throwable throwable, int statusCode) {
				// TODO Auto-generated method stub
				
			}
		}).execute();
	}

	@Override
	public void go(HasWidgets container) {
		container.clear();
		container.add(view.asWidget());
		GWT.runAsync(new RunAsyncCallback() {
			public void onFailure(Throwable caught) {
				Window.alert("Code download failed");
			}

			public void onSuccess() {
				updateTextView();
			}
		});
	}

	private void resetConfiguration() {
		System.out.println("(TextPresenter) Resetting Screen Configuration...");
		setConfiguration(0, null, null, null);
	}

	private void setConfiguration(int screenId, String screenName, String url, List<Text> textConfigs) {
		System.out.println("(TextPresenter) Updating Screen Configuration");
		if (screenConfiguration == null) {
			screenConfiguration = screenConfigurationFactory.screenConfiguration().as();
		}
		screenConfiguration.setId(screenId);
		screenConfiguration.setName(screenName);
		screenConfiguration.setUrl(url);
		screenConfiguration.setText(textConfigs);
		updateTextView();
	}

	private void updateTextView() {
		System.out.println("(TextPresenter) Updating View with Text Configuration");
		createCopiesOfConfigs(screenConfiguration.getText());
		view.updateTextConfigurations(screenConfiguration.getText());
	}

	private void createCopiesOfConfigs(List<Text> textConfigs) {
		if (textConfigs == null || textConfigs.isEmpty()) {
			originalTextConfigs = null;
			modifiedTextConfigs = null;
			return;
		}
		// FIXME: Work around as the order of configs is not fixed
//		Collections.sort(textConfigs);

		originalTextConfigs = new HashMap<String, Text>(textConfigs.size());
		modifiedTextConfigs = new HashMap<String, Text>(textConfigs.size());
		for (Text textConfig : textConfigs) {
			originalTextConfigs.put(textConfig.createElementId(), textConfig);
			Text copyConfig = createCopy(textConfig);
			modifiedTextConfigs.put(copyConfig.createElementId(), copyConfig);
		}
	}

	// TODO: remove this method once issue with ConfigEditorView is fixed
	private List<Text> getModifiedText() {
		Collection<Text> modifiedCollection = modifiedTextConfigs.values();
		List<Text> textConfigs = Arrays.asList(modifiedCollection.toArray(new Text[modifiedCollection.size()]));
		return textConfigs;
	}

	private Text createCopy(Text textConfig) {
		if (textConfig == null) {
			return null;
		}
		Text result = null;
		if (textConfig instanceof TextEntry) {
			TextEntry source = (TextEntry) textConfig;
			TextEntry target = screenConfigurationFactory.textEntry().as();
			target.setValue(source.getValue());
			result = target;
		} else if (textConfig instanceof ErrorText) {
			ErrorText source = (ErrorText) textConfig;
			ErrorText target = screenConfigurationFactory.errorText().as();
			target.setCode(source.getCode());
			target.setMessage(source.getMessage());
			result = target;
		}
		result.setId(textConfig.getId());
		result.setName(textConfig.getName());
		// result.setValue(textConfig.getValue());
		result.setCategory(textConfig.getCategory());
		result.setMultiLine(textConfig.isMultiLine());

		FontOptions originalFonts = textConfig.getFontOptions();
		if (originalFonts != null) {
			FontOptions fontsCopy = screenConfigurationFactory.fontOptions().as();
			fontsCopy.setId(originalFonts.getId());
			fontsCopy.setName(originalFonts.getName());
			fontsCopy.setStyle(originalFonts.getStyle());
			fontsCopy.setSize(originalFonts.getSize());
			fontsCopy.setAlignment(originalFonts.getAlignment());
			fontsCopy.setColor(originalFonts.getColor());
			result.setFontOptions(fontsCopy);
		}
		return result;
	}

	public class TextConfigChangeHandler implements ChangeHandler {

		private static final int INDEX_ZERO = 0;

		public TextConfigChangeHandler() {
			super();
		}

		@Override
		public void onChange(ChangeEvent event) {

			FontOptionsView fontOptionsView = view.getFontOptionsView();
			ConfigEditorView configEditorView = view.getConfigEditorView();
			ValueBoxBase<String> associatedWidget = fontOptionsView.getAssociatedWidget();

			if (event.getSource() instanceof TextBoxBase) {
				TextBoxBase eventSrcTextBase = (TextBoxBase) event.getSource();
				eventSrcTextBase.setText(eventSrcTextBase.getText().trim());
				String eventSrcWidgetId = eventSrcTextBase.getElement().getId();
				String newText = eventSrcTextBase.getText();
				if (newText.isEmpty()) {
					Text originalConfig = originalTextConfigs.get(eventSrcWidgetId);
					if (originalConfig instanceof TextEntry) {
						newText = ((TextEntry) originalConfig).getValue();
					} else if (originalConfig instanceof ErrorText) {
						newText = ((ErrorText) originalConfig).getMessage();
					}
				}
				Text modifiedConfig = modifiedTextConfigs.get(eventSrcWidgetId);
				if (modifiedConfig instanceof TextEntry) {
					((TextEntry) modifiedConfig).setValue(newText);
				} else if (modifiedConfig instanceof ErrorText) {
					((ErrorText) modifiedConfig).setMessage(newText);
				}
				boolean configChanged = false;
				for (Text textConfig : screenConfiguration.getText()) {
					if (!textConfig.equals(modifiedTextConfigs.get(textConfig.createElementId()))) {
						configChanged = configChanged | true;
						break;
					}
				}
				configEditorView.disableConfirmControl();
				if (configChanged) {
					configEditorView.enablePreviewControl();
					// controls.enableUndoControl();
				} else {
					configEditorView.disablePreviewControl();
					configEditorView.disableConfirmControl();
					// controls.disableUndoControl();
				}

			} else if (event.getSource() instanceof ListBox) {
				ListBox eventSrcListBox = (ListBox) event.getSource();
				int index = eventSrcListBox.getSelectedIndex();
				if (index <= 0 | associatedWidget == null) {
					fontOptionsView.setSelectedStyle(INDEX_ZERO);
					fontOptionsView.setSelectedSize(INDEX_ZERO);
					return;
				}

				FontOptions modifiedFonts = modifiedTextConfigs.get(associatedWidget.getElement().getId())
						.getFontOptions();
				configEditorView.disableConfirmControl();
				if (eventSrcListBox.equals(fontOptionsView.getFontStylesListBox())) {
					fontOptionsView.setSelectedStyle(index);
					modifiedFonts.setStyle(fontOptionsView.getSelectedStyle());
				} else if (eventSrcListBox.equals(fontOptionsView.getFontSizesListBox())) {
					fontOptionsView.setSelectedSize(index);
					modifiedFonts.setSize(fontOptionsView.getSelectedSize());
				}
				associatedWidget.fireEvent(new ChangeEvent() {
				});
			}
		}
	}

	public class TextConfigFocusHandler implements FocusHandler {

		public TextConfigFocusHandler() {
			super();
		}

		@Override
		public void onFocus(FocusEvent event) {
			TextBoxBase textBox = (TextBoxBase) event.getSource();
			String txtId = textBox.getElement().getId();
			// Set font options for a text box
			FontOptions modifiedFonts = modifiedTextConfigs.get(txtId).getFontOptions();
			int stylesIndex = FontOptionsWidget.FONT_STYLES.indexOf(modifiedFonts.getStyle());
			// TODO
			FontOptionsView fontOptionsView = view.getFontOptionsView();
			fontOptionsView.setSelectedStyle(stylesIndex);
			int sizeIndex = FontOptionsWidget.FONT_SIZES.indexOf(String.valueOf(modifiedFonts.getSize()));
			fontOptionsView.setSelectedSize(sizeIndex);
			fontOptionsView.setSelectedAlignment(modifiedFonts.getAlignment());
			fontOptionsView.setSelectedColor(modifiedFonts.getColor());
			if (fontOptionsView.getAssociatedWidget() != null) {
				fontOptionsView.getAssociatedWidget().setStyleName("gwt-TextBox");
			}
			textBox.setStyleName("borderForSelected");
			fontOptionsView.setAssociatedWidget(textBox);
		}
	}

	public class FontOptionsClickHandler implements ClickHandler {

		public FontOptionsClickHandler() {
			super();
		}

		@Override
		public void onClick(ClickEvent event) {
			FontOptionsView fontOptionsView = view.getFontOptionsView();
			ConfigEditorView configEditorView = view.getConfigEditorView();

			ValueBoxBase<String> associatedWidget = fontOptionsView.getAssociatedWidget();
			if (associatedWidget == null) {
				return;
			}
			FontOptions modifiedFonts = modifiedTextConfigs.get(associatedWidget.getElement().getId()).getFontOptions();
			if (event.getSource() instanceof Image) {
				Image alignmentImg = (Image) event.getSource();
				final Alignment currentAlignment = fontOptionsView.getAlignmentSelected();

				if (!currentAlignment.name().equals(alignmentImg.getElement().getId())) {
					configEditorView.disableConfirmControl();
					fontOptionsView.setSelectedAlignment(Alignment.valueOf(alignmentImg.getElement().getId()));
					modifiedFonts.setAlignment(fontOptionsView.getAlignmentSelected());
					associatedWidget.fireEvent(new ChangeEvent() {
					});
				}
			} else if (event.getSource() instanceof HTML) {
				String newColor = ((HTML) event.getSource()).getElement().getId();
				final String colorSelected = fontOptionsView.getColorSelected();
				if (!newColor.equals(colorSelected)) {
					configEditorView.disableConfirmControl();
					fontOptionsView.setSelectedColor(newColor);
					modifiedFonts.setColor(fontOptionsView.getColorSelected());
					associatedWidget.fireEvent(new ChangeEvent() {
					});
				}
			}
		}
	}
}